{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0894d6aa",
   "metadata": {},
   "source": [
    "# **CV Checker by Omer Hausner**\n",
    "---\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "03310fb1",
   "metadata": {},
   "source": [
    "# Introduction\n",
    "---\n",
    "In this notebook, I prepared a model that compares you CV resume against a job desctiption, and summerize the pros and cons in your resume according to the job's requirements. Finally, the model will suggest steps to improve your cv for the job application."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "efd26b16",
   "metadata": {},
   "source": [
    "# Imports & Installations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e328019d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install pdfplumber if not already installed - to allow PDF text extraction\n",
    "!uv pip install pdfplumber\n",
    "! uv pip install docx2txt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8ebfd2b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "from openai import OpenAI\n",
    "from IPython.display import Markdown, display, update_display\n",
    "\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "import docx2txt\n",
    "import pdfplumber\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bbdc32d",
   "metadata": {},
   "source": [
    "# Config"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "119a4a3d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# set OPENAI_API_KEY\n",
    "\n",
    "load_dotenv(override=True)\n",
    "api_key = os.getenv('OPENAI_API_KEY')\n",
    "\n",
    "if api_key and api_key.startswith('sk-proj-') and len(api_key)>10:\n",
    "    print(\"API key looks good so far\")\n",
    "else:\n",
    "    print(\"There might be a problem with your API key? Please visit the troubleshooting notebook!\")\n",
    "    \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7310bbdd",
   "metadata": {},
   "source": [
    "# Preprocessing\n",
    "---\n",
    "In this section we will preprocess the functions and variables we need for the final inference model."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e90cd8c5",
   "metadata": {},
   "source": [
    "# Scraper Utils"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f0e53f93",
   "metadata": {},
   "outputs": [],
   "source": [
    "from bs4 import BeautifulSoup\n",
    "import requests\n",
    "\n",
    "\n",
    "# Standard headers to fetch a website\n",
    "headers = {\n",
    "    \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\"\n",
    "}\n",
    "\n",
    "\n",
    "def fetch_website_contents(url):\n",
    "    \"\"\"\n",
    "    Return the title and contents of the website at the given url;\n",
    "    truncate to 2,000 characters as a sensible limit\n",
    "    \"\"\"\n",
    "    response = requests.get(url, headers=headers)\n",
    "    soup = BeautifulSoup(response.content, \"html.parser\")\n",
    "    title = soup.title.string if soup.title else \"No title found\"\n",
    "    if soup.body:\n",
    "        for irrelevant in soup.body([\"script\", \"style\", \"img\", \"input\"]):\n",
    "            irrelevant.decompose()\n",
    "        text = soup.body.get_text(separator=\"\\n\", strip=True)\n",
    "    else:\n",
    "        text = \"\"\n",
    "    return (title + \"\\n\\n\" + text)[:3_000]\n",
    "\n",
    "\n",
    "def fetch_website_links(url):\n",
    "    \"\"\"\n",
    "    Return the links on the webiste at the given url\n",
    "    I realize this is inefficient as we're parsing twice! This is to keep the code in the lab simple.\n",
    "    Feel free to use a class and optimize it!\n",
    "    \"\"\"\n",
    "    response = requests.get(url, headers=headers)\n",
    "    soup = BeautifulSoup(response.content, \"html.parser\")\n",
    "    links = [link.get(\"href\") for link in soup.find_all(\"a\")]\n",
    "    return [link for link in links if link]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a20cfa0",
   "metadata": {},
   "source": [
    "## Resume Guidelines\n",
    "For the purpose of this task, I chose a website which contained relevant resume guidelines which will help the LLM model to acknowledge basic requirements in a resume."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "70b84ba6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# This url contains guidelines on how to write a good resume.\n",
    "# You may change it to any other url you like, or leave it as None if you don't want to use any guidelines.\n",
    "resume_guidlines_url = \"https://nationalcareers.service.gov.uk/careers-advice/cv-sections\" \n",
    "\n",
    "# We now fetch the contents of the resume guidelines website.\n",
    "resume_guidlines = fetch_website_contents(resume_guidlines_url) if resume_guidlines_url else None"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1dd0a88e",
   "metadata": {},
   "source": [
    "## Extract resume to text\n",
    "We now define functions that take a file path of the desired resume, and extract it as plain text. \n",
    "\n",
    "This function supports files such as Word (.docx) and PDF (.pdf) only."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "487b31f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Text Extraction Functions\n",
    "\n",
    "def extract_text_from_pdf(pdf_path: str) -> None:\n",
    "    \"\"\"\n",
    "    Extract text from a PDF file and save it to a text file.\n",
    "    \"\"\"\n",
    "    with pdfplumber.open(pdf_path) as pdf:\n",
    "        all_text = ''\n",
    "        for page in pdf.pages:\n",
    "            all_text += page.extract_text() + '\\n'\n",
    "    \n",
    "    return all_text\n",
    "\n",
    "def extract_text_from_docx(docx_path: str) -> None:\n",
    "    \"\"\"\n",
    "    Extract text from a DOCX file and save it to a text file.\n",
    "    \"\"\"\n",
    "    all_text = docx2txt.process(docx_path)\n",
    "    \n",
    "    return all_text\n",
    "\n",
    "def extract_text_from_resume(resume_path: str) -> None:\n",
    "    \"\"\"\n",
    "    Extract text from a resume file (PDF or DOCX) and save it to a text file.\n",
    "    \"\"\"\n",
    "    _, file_extension = os.path.splitext(resume_path)\n",
    "    if file_extension.lower() == '.pdf':\n",
    "        text = extract_text_from_pdf(resume_path)\n",
    "    elif file_extension.lower() == '.docx':\n",
    "        text = extract_text_from_docx(resume_path)\n",
    "    else:\n",
    "        raise ValueError(\"Unsupported file format. Please provide a PDF or DOCX file.\")\n",
    "\n",
    "    return text\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f7299082",
   "metadata": {},
   "outputs": [],
   "source": [
    "## Job description - Example\n",
    "job_desc_url = \"https://www.linkedin.com/jobs/view/4336621982/?alternateChannel=search&eBP=NOT_ELIGIBLE_FOR_CHARGING&trk=d_flagship3_search_srp_jobs&refId=%2BvHkE19BdH5zD0S1GtNrEg%3D%3D&trackingId=d7wYUZZ%2F%2BgVrwN%2FnD2sKlw%3D%3D\"\n",
    "job_description = fetch_website_contents(job_desc_url)  # Replace with actual job description URL"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19336677",
   "metadata": {},
   "outputs": [],
   "source": [
    "job_description"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "838514bd",
   "metadata": {},
   "source": [
    "## Set system and user messages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ad4226c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# System Message\n",
    "def set_system_message(resume_guidlines_url):\n",
    "    resume_guidlines = fetch_website_contents(resume_guidlines_url) if resume_guidlines_url else None\n",
    "    \n",
    "    system_message = f\"\"\"\n",
    "    You are an expert career advisor helping people improve their CVs (resumes) for specific job applications.\n",
    "    Your task is to analyze a user's CV against a given job description and provide feedback on how well the CV matches the job requirements.\n",
    "    You will provide a summary of strengths and weaknesses in the CV relative to the job description, and suggest specific improvements to better align the CV with the job.\n",
    "    Consider also the overall presentation, clarity, structure and relevance of the CV content.\n",
    "    Use the following guidelines to evaluate the CV:\n",
    "    {resume_guidlines if resume_guidlines else \"No specific guidelines provided.\"}\n",
    "    Be concise and focus on actionable feedback, up to 4 main bullet points in each section. \n",
    "\n",
    "    Provide your response in the following format:\n",
    "    1. General Feedback:\n",
    "    2. Summary of Strengths:\n",
    "    3. Summary of Weaknesses:\n",
    "    4. Suggested Improvements:\n",
    "\n",
    "    Respond in markdown format, use headings and bullet points where appropriate, and emojis to enhance readability.\n",
    "    Keep the response not too long, ideally under 300 words.\n",
    "    \n",
    "    \"\"\"\n",
    "    return system_message"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "df69c5a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def set_user_message(resume_path: str, job_description_url: str) -> str:\n",
    "    job_description = fetch_website_contents(job_description_url)\n",
    "    resume_text = extract_text_from_resume(resume_path)\n",
    "\n",
    "    user_message = f\"\"\"\n",
    "    Here is the job description:\n",
    "    {job_description}\n",
    "\n",
    "    Here is the my CV:\n",
    "    {resume_text}\n",
    "\n",
    "    Please analyze the CV against the job description and provide your feedback.\n",
    "    \"\"\"\n",
    "    return user_message"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1cf9c107",
   "metadata": {},
   "outputs": [],
   "source": [
    "def set_messages(resume_guidlines_url, job_description_url, resume_path):\n",
    "    system_message = set_system_message(resume_guidlines_url)\n",
    "    user_message = set_user_message(resume_path, job_description_url)\n",
    "    \n",
    "    messages = [\n",
    "        {\"role\": \"system\", \"content\": system_message},\n",
    "        {\"role\": \"user\", \"content\": user_message}\n",
    "    ]\n",
    "    \n",
    "    return messages"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5eb6a16",
   "metadata": {},
   "source": [
    "# Set Final Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "05ee7569",
   "metadata": {},
   "outputs": [],
   "source": [
    "openai = OpenAI()\n",
    "MODEL = 'gpt-4.1-mini'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b17f0c8a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def cv_checker_model(resume_guidlines_url, job_description_url, resume_path, model=\"gpt-5-nano\"):\n",
    "    messages = set_messages(resume_guidlines_url, job_description_url, resume_path)\n",
    "    response = openai.chat.completions.create(model=model, messages=messages)\n",
    "\n",
    "    display(Markdown(response.choices[0].message.content))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9518244",
   "metadata": {},
   "source": [
    "## Set Variables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "06b7deb1",
   "metadata": {},
   "outputs": [],
   "source": [
    "resume_guidelines_url = \"https://nationalcareers.service.gov.uk/careers-advice/cv-sections\"\n",
    "\n",
    "## CHANGE HERE\n",
    "job_description_url = \"https://www.linkedin.com/jobs/view/4336621982/?alternateChannel=search&eBP=NOT_ELIGIBLE_FOR_CHARGING&trk=d_flagship3_search_srp_jobs&refId=%2BvHkE19BdH5zD0S1GtNrEg%3D%3D&trackingId=d7wYUZZ%2F%2BgVrwN%2FnD2sKlw%3D%3D\"\n",
    "resume_path = r\"enter_your_resume_path_here.pdf\"\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc2e31b2",
   "metadata": {},
   "source": [
    "## Run Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "908d29e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "cv_checker_model(resume_guidelines_url, job_description_url, resume_path, model=MODEL)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "llm-engineering (3.12.12)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
